// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © fluxchart
//@version=5

const bool DEBUG = false
const int maxBoxesCount = 500
const int maxBarsBack = 100
string precisionTF = timeframe.period

indicator(title = 'HTF Inversion Fair Value Gap | Flux Charts', shorttitle = "HTF IFVG | Flux Charts", overlay = true, max_boxes_count = maxBoxesCount, max_labels_count = maxBoxesCount, max_lines_count = maxBoxesCount, max_bars_back = maxBarsBack)

const int showLastXFVGs = 10
const int extendLastXFVGsCount = 3
const int minimumFVGSize = 0
const int minimumIFVGSize = 0
const int atrLen = 10

var int curTFMMS = timeframe.in_seconds()

var bool initRun = true
timeframe1 = input.timeframe("240", title = "Higher Timeframe", group = "General Configuration", display = display.none)
ifvgEndMethod = input.string("Wick", "IFVG Zone Invalidation", options = ["Wick", "Close"],  group = "General Configuration")

if timeframe.in_seconds(timeframe1) <= timeframe.in_seconds()
    runtime.error("Timeframe selected must be higher than the current timeframe.")

showInvalidated = true
maxDistanceToLastBarText = "High"
maxDistanceToLastBar = (maxDistanceToLastBarText == "High" ? 1500 : maxDistanceToLastBarText == "Normal" ? 900 : maxDistanceToLastBarText == "Low" ? 750 : 500)
maxATRMult = DEBUG ? input.float(3.5, "Max ATR Multiplier", group = "General Configuration", display = display.none) : 3.5

fvgEnabled = DEBUG ? input.bool(true, "Enabled", group = "Fair Value Gaps", inline="EV", display = display.none) : true
fvgVolumetricInfo = false
fvgEndMethod = input.string("Wick", "Zone Invalidation", options = ["Wick", "Close"],  group = "Fair Value Gaps")
fvgFilterMethod = input.string("Average Range", "Zone Filtering", options = ["Average Range", "Volume Threshold"],  group = "Fair Value Gaps")
volumeThresholdPercent = input.int(50, "Volume Threshold %", group = "Fair Value Gaps", tooltip = "Only taken into calculation when filter method is Volume Threshold.", minval = 1, maxval = 200)
fvgBars = input.string("Same Type", "FVG Detection", options = ["Same Type", "All"], tooltip = "Same Type -> All 3 bars that formed the FVG should be the same type. (Bullish / Bearish) \n\nAll -> Bar types may vary between bullish / bearish.", group = "Fair Value Gaps")
fvgSensEnabled = true
fvgSensitivityText = input.string("Extreme", "Detection Sensitivity", options = ["All", "Extreme", "High", "Normal", "Low"], group = "Fair Value Gaps")
combineFVGs = DEBUG ? input.bool(true, "Combine Zones", group = "Fair Value Gaps") : true
allowGaps = input.bool(false, "Allow Gaps Between Bars", group = "Fair Value Gaps", tooltip = "On tickers that can have a different opening price than the previous bar's closing price, the indicator will not analyze those bars for FVGs if disabled.\n\nFor Example, if the today's opening price is different from the yesterday's closing price in a stock ticker.")
deleteUntouched = DEBUG ? input.bool(true, "", group = "Fair Value Gaps", inline = "deleteUntouched") : true
deleteUntouchedAfterXBars = DEBUG ? input.int(500, "Delete Untouched Zones After", group = "Fair Value Gaps", minval = 5, maxval = 500, inline = "deleteUntouched") : 200

ifvgEnabled = DEBUG ? input.bool(false, "Enabled", inline="EV", group = "Inversion Fair Value Gaps") : false
ifvgVolumetricInfo = false
ifvgFull = DEBUG ? input.bool(true, "IFVG Full", group = "Inversion Fair Value Gaps", display = display.none) : true

extendZonesBy = DEBUG ? input.int(15, "Extend Zones", group = "Style", minval = 1, maxval = 30, inline = "ExtendZones") : 15
extendZonesDynamic = DEBUG ? input.bool(true, "Dynamic", group = "Style", inline = "ExtendZones") : true
changeCombinedFVGsColor = DEBUG ? input.bool(false, "Change Combined Zones Color", group = "Style", inline = "CombinedColor") : false
combinedText = DEBUG ? input.bool(false, "Combined Text", group = "Style", inline = "CombinedColor") : false
combinedColor = DEBUG ? input.color(#fff70080, DEBUG ? "" : "Combined Zone Color", group = "Style", inline = "CombinedColor") : #fff70080
startZoneFrom = DEBUG ? input.string("Last Bar", "Start FVG Zones From", options = ["First Bar", "Last Bar"], group = "Style"): "Last Bar"
volumeBarsPlace = DEBUG ? input.string("Left", "Show Volume Bars At", options = ["Left", "Right"], group = "Style", inline = "volumebars") : "Left"
mirrorVolumeBars = DEBUG ? input.bool(true, "Mirror Volume Bars", group = "Style", inline = "volumebars") : true

volumeBarsLeftSide = (volumeBarsPlace == "Left")
fvgSensitivity = fvgSensitivityText == "All" ? 50 : fvgSensitivityText == "Extreme" ? 6 : fvgSensitivityText == "High" ? 2 : fvgSensitivityText == "Normal" ? 1.5 : 1
extendZonesByTime = extendZonesBy * timeframe.in_seconds(timeframe.period) * 1000

tableEnabled = input.bool(true, "Dashboard", group = "Dashboard", display = display.none)
tableLocation = input.string("Top Right", "Position", options = ["Top Right", "Top Center", "Right Center", "Middle Center", "Left Center", "Bottom Center"], group = "Dashboard", display = display.none)
screenerColor = input.color(#1B1F2B, 'Background',  group = 'Dashboard', display = display.none)
frameColor = input.color(color.rgb(255, 255, 255), 'Frame', group = 'Dashboard', display = display.none)
borderColor = input.color(color.rgb(255, 255, 255), 'Border', group = 'Dashboard', display = display.none)
textColor = input.color(color.white, 'Text', group = 'Dashboard', display = display.none)

lineOffset = input.int(25, "Offset", minval = 1, group = 'Customization')
fvgWidth = input.int(20, "Width", minval = 1, group = 'Customization')
fvgBullColor = input(#08998180, 'Bullish', inline = 'fvgColor', group = 'Customization', display = display.none)
fvgBearColor = input(#f2364680, 'Bearish', inline = 'fvgColor', group = 'Customization', display = display.none)

atr = ta.atr(atrLen)
volCheck = (ta.cum(volume) > 0)

insideBounds = (bar_index > last_bar_index - maxDistanceToLastBar) and barstate.isconfirmed

bullishInverseColor = #08998180
bearishInverseColor = #f2364580

getPosition (positionText) =>
    if positionText == "Top Right"
        position.top_right
    else if positionText == "Top Center"
        position.top_center
    else if positionText == "Right Center"
        position.middle_right
    else if positionText == "Left Center"
        position.middle_left
    else if positionText == "Bottom Center"
        position.bottom_center
    else if positionText == "Middle Center"
        position.middle_center

type FVGInfo
    float max = na
    float min = na
    bool isBull = na
    int t = na
    float totalVolume = na
    int startBarIndex = na
    int endBarIndex = na
    int genesisTime = na
    int startTime = na
    int endTime = na
    float endLow = na
    float endHigh = na
    bool combined = false
    string combinedTimeframesStr = na
    bool disabled = false
    string timeframeStr = na

    float lowVolume = na
    float highVolume = na
    bool isInverse = false
    int lastTouched = na
    int lastTouchedIFVG = na
    int inverseEndIndex = na
    int inverseEndTime = na
    float inverseVolume

    float afterInverseMax
    float afterInverseMin

createFVGInfo (h, l, bull, t, tv) =>
    FVGInfo newFVGInfo = FVGInfo.new(h, l, bull, t, tv)
    newFVGInfo

type FVG
    FVGInfo info = na
    
    bool isRendered = false

    box fvgBox = na
    box ifvgBox = na
    box fvgBoxText = na
    box fvgBoxPositive = na
    box fvgBoxNegative = na

    line fvgSeperator = na
    line fvgTextSeperator = na

createFVG (FVGInfo FVGInfoF) =>
    FVG newFVG = FVG.new(FVGInfoF)
    newFVG

safeDeleteFVG (FVG fvg) =>
    fvg.isRendered := false

    box.delete(fvg.fvgBox)
    box.delete(fvg.ifvgBox)
    box.delete(fvg.fvgBoxText)
    box.delete(fvg.fvgBoxPositive)
    box.delete(fvg.fvgBoxNegative)

    line.delete(fvg.fvgSeperator)
    line.delete(fvg.fvgTextSeperator)

type timeframeInfo
    int index = na
    string timeframeStr = na
    bool isEnabled = false

    FVGInfo[] FVGInfoList = na

newTimeframeInfo (index, timeframeStr, isEnabled) =>
    newTFInfo = timeframeInfo.new()
    newTFInfo.index := index
    newTFInfo.isEnabled := isEnabled
    newTFInfo.timeframeStr := timeframeStr
    if newTFInfo.timeframeStr == ""
        newTFInfo.timeframeStr := timeframe.period

    newTFInfo.FVGInfoList := array.new<FVGInfo>(0)

    newTFInfo
// ____ TYPES END ____

var timeframeInfo[] timeframeInfos = array.from(newTimeframeInfo(1, timeframe1, true))
var FVGInfo[] FVGInfoList = array.new<FVGInfo>(0)

var FVG[] allFVGList = array.new<FVG>(0)

[hi0, lo0, ti0] = request.security_lower_tf(syminfo.tickerid, precisionTF, [high, low, time])
hi1 = hi0[1]
lo1 = lo0[1]
ti1 = ti0[1]

hi2 = hi0[2]
lo2 = lo0[2]
ti2 = ti0[2]

hi3 = hi0[3]
lo3 = lo0[3]
ti3 = ti0[3]

findValRtnTime (valToFind, offs, toSearch, searchMode) =>
    array<float> lo = (offs == 0 ? lo0 : offs == 1 ? lo1 : offs == 2 ? lo2 : lo3)
    array<float> hi = (offs == 0 ? hi0 : offs == 1 ? hi1 : offs == 2 ? hi2 : hi3)
    array<int> ti = (offs == 0 ? ti0 : offs == 1 ? ti1 : offs == 2 ? ti2 : ti3)
    
    int rtnTime = na
    float minDiff = na
    if toSearch == "Low" and not na(lo)
        if (lo).size() > 0
            for i = (lo).size() - 1 to 0
                curLow = (lo).get(i)
                if searchMode == "Nearest"
                    curDiff = math.abs(valToFind - curLow)
                    if na(minDiff)
                        rtnTime := (ti).get(i)
                        minDiff := curDiff
                    else
                        if curDiff <= minDiff
                            minDiff := curDiff
                            rtnTime := (ti).get(i)
                if searchMode == "Higher"
                    if curLow >= valToFind
                        rtnTime := (ti).get(i)
                if searchMode == "Lower"
                    if curLow <= valToFind
                        rtnTime := (ti).get(i)
    else if not na(hi)
        if (hi).size() > 0
            for i = (hi).size() - 1 to 0
                curHigh = (hi).get(i)
                if searchMode == "Nearest"
                    curDiff = math.abs(valToFind - curHigh)
                    if na(minDiff)
                        rtnTime := (ti).get(i)
                        minDiff := curDiff
                    else
                        if curDiff <= minDiff
                            minDiff := curDiff
                            rtnTime := (ti).get(i)
                if searchMode == "Higher"
                    if curHigh >= valToFind
                        rtnTime := (ti).get(i)
                if searchMode == "Lower"
                    if curHigh <= valToFind
                        rtnTime := (ti).get(i)
    rtnTime

findValRtnAll (valToFind, offs, toSearch, searchMode) =>
    array<float> lo = (offs == 0 ? lo0 : offs == 1 ? lo1 : offs == 2 ? lo2 : lo3)
    array<float> hi = (offs == 0 ? hi0 : offs == 1 ? hi1 : offs == 2 ? hi2 : hi3)
    array<int> ti = (offs == 0 ? ti0 : offs == 1 ? ti1 : offs == 2 ? ti2 : ti3)
    
    float rtnLow = na
    float rtnHigh = na
    int rtnTime = na

    float minDiff = na
    if toSearch == "Low" and not na(lo)
        if (lo).size() > 0
            for i = (lo).size() - 1 to 0
                curLow = (lo).get(i)
                if searchMode == "Nearest"
                    curDiff = math.abs(valToFind - curLow)
                    if na(minDiff)
                        rtnTime := (ti).get(i)
                        rtnLow := lo.get(i)
                        rtnHigh := hi.get(i)
                        minDiff := curDiff
                    else
                        if curDiff <= minDiff
                            minDiff := curDiff
                            rtnTime := (ti).get(i)
                            rtnLow := lo.get(i)
                            rtnHigh := hi.get(i)
                if searchMode == "Higher"
                    if curLow >= valToFind
                        rtnTime := (ti).get(i)
                        rtnLow := lo.get(i)
                        rtnHigh := hi.get(i)
                if searchMode == "Lower"
                    if curLow <= valToFind
                        rtnTime := (ti).get(i)
                        rtnLow := lo.get(i)
                        rtnHigh := hi.get(i)
    else if not na(hi)
        if (hi).size() > 0
            for i = (hi).size() - 1 to 0
                curHigh = (hi).get(i)
                if searchMode == "Nearest"
                    curDiff = math.abs(valToFind - curHigh)
                    if na(minDiff)
                        rtnTime := (ti).get(i)
                        rtnLow := lo.get(i)
                        rtnHigh := hi.get(i)
                        minDiff := curDiff
                    else
                        if curDiff <= minDiff
                            minDiff := curDiff
                            rtnTime := (ti).get(i)
                            rtnLow := lo.get(i)
                            rtnHigh := hi.get(i)
                if searchMode == "Higher"
                    if curHigh >= valToFind
                        rtnTime := (ti).get(i)
                        rtnLow := lo.get(i)
                        rtnHigh := hi.get(i)
                if searchMode == "Lower"
                    if curHigh <= valToFind
                        rtnTime := (ti).get(i)
                        rtnLow := lo.get(i)
                        rtnHigh := hi.get(i)
    [rtnLow, rtnHigh, rtnTime]

moveLine(_line, _x, _y, _x2) =>
    line.set_xy1(_line, _x,  _y)
    line.set_xy2(_line, _x2, _y)

moveBox (_box, _topLeftX, _topLeftY, _bottomRightX, _bottomRightY) =>
    box.set_lefttop(_box, _topLeftX, _topLeftY)
    box.set_rightbottom(_box, _bottomRightX, _bottomRightY)

isTimeframeLower (timeframe1F, timeframe2F) =>
    timeframe.in_seconds(timeframe1F) < timeframe.in_seconds(timeframe2F)

getMinTimeframe (timeframe1F, timeframe2F) =>
    if isTimeframeLower(timeframe1F, timeframe2F)
        timeframe1F
    else
        timeframe2F

getMaxTimeframe (timeframe1F, timeframe2F) =>
    if isTimeframeLower(timeframe1F, timeframe2F)
        timeframe2F
    else
        timeframe1F

formatTimeframeString (formatTimeframe) =>
    timeframeF = formatTimeframe == "" ? timeframe.period : formatTimeframe
    
    if str.contains(timeframeF, "D") or str.contains(timeframeF, "W") or str.contains(timeframeF, "S") or str.contains(timeframeF, "M")
        timeframeF
    else
        seconds = timeframe.in_seconds(timeframeF)
        if seconds >= 3600
            hourCount = int(seconds / 3600)
            str.tostring(hourCount) + " Hour" + (hourCount > 1 ? "s" : "")
        else
            timeframeF + " Min"

colorWithTransparency (colorF, transparencyX) =>
    color.new(colorF, color.t(colorF) * transparencyX)

createFVGBox (boxColor, transparencyX = 1.0, xlocType = xloc.bar_time) =>
    box.new(na, na, na, na, text_size = size.normal, xloc = xlocType, extend = extend.none, bgcolor = colorWithTransparency(boxColor, transparencyX), text_color = textColor, text_halign = text.align_center, border_color = #00000000)

renderFVG (FVG fvg) =>
    fvg.isRendered := true
    if fvgEnabled and ((not ifvgEnabled) or (not fvg.info.isInverse)) and (showInvalidated or na(fvg.info.endTime))
        fvg.fvgBox := createFVGBox((fvg.info.combined and changeCombinedFVGsColor) ? combinedColor : fvg.info.isBull ? fvgBullColor : fvgBearColor, 1.5)
        fvg.fvgBoxText := createFVGBox(color.new(color.white, 100))
        if fvgVolumetricInfo
            fvg.fvgBoxPositive := createFVGBox(fvgBullColor)
            fvg.fvgBoxNegative := createFVGBox(fvgBearColor)
            fvg.fvgSeperator := line.new(na,na,na,na,xloc.bar_time,extend.none,textColor,line.style_dashed,1)
            fvg.fvgTextSeperator := line.new(na,na,na,na,xloc.bar_time,extend.none,textColor,line.style_solid,1)

        zoneSize = extendZonesDynamic ? na(fvg.info.endTime) ? extendZonesByTime : ((fvg.info.endTime - curTFMMS * 1000) - fvg.info.startTime) : extendZonesByTime
        if na(fvg.info.endTime)
            zoneSize := (time + 1) - fvg.info.startTime

        startX = volumeBarsLeftSide ? fvg.info.startTime : fvg.info.startTime + zoneSize - zoneSize / 3
        maxEndX = volumeBarsLeftSide ? fvg.info.startTime + zoneSize / 3 : fvg.info.startTime + zoneSize

        moveBox(fvg.fvgBox, fvg.info.startTime, fvg.info.max, fvg.info.startTime + zoneSize, fvg.info.min)
        moveBox(fvg.fvgBoxText, volumeBarsLeftSide ? maxEndX : fvg.info.startTime, fvg.info.max, volumeBarsLeftSide ? fvg.info.startTime + zoneSize : startX, fvg.info.min)

        percentage = int((math.min(fvg.info.highVolume, fvg.info.lowVolume) / math.max(fvg.info.highVolume, fvg.info.lowVolume)) * 100.0)
        FVGText = (na(fvg.info.combinedTimeframesStr) ? formatTimeframeString(fvg.info.timeframeStr) : fvg.info.combinedTimeframesStr) + " FVG"
        box.set_text(fvg.fvgBoxText, (fvgVolumetricInfo ? str.tostring(fvg.info.totalVolume, format.volume) + " (" + str.tostring(percentage) + "%)\n" : "") + (combinedText and fvg.info.combined ? "[Combined]\n" : "") + FVGText)
        
        if fvg.info.combined and not changeCombinedFVGsColor
            fvg.fvgBox.set_bgcolor(colorWithTransparency(fvg.info.isBull ? fvgBullColor : fvgBearColor, 1.1))

        if fvgVolumetricInfo
            showHighLowBoxText = false

            curEndXHigh = int(math.ceil((fvg.info.highVolume / fvg.info.totalVolume) * (maxEndX - startX) + startX))
            curEndXLow = int(math.ceil((fvg.info.lowVolume / fvg.info.totalVolume) * (maxEndX - startX) + startX))

            curEndXHigh := nz(curEndXHigh, maxEndX)
            curEndXLow := nz(curEndXLow, maxEndX)

            moveBox(fvg.fvgBoxPositive, mirrorVolumeBars ? startX : curEndXLow, fvg.info.max, mirrorVolumeBars ? curEndXHigh : maxEndX, (fvg.info.min + fvg.info.max) / 2)
            box.set_text(fvg.fvgBoxPositive, showHighLowBoxText ? str.tostring(fvg.info.highVolume, format.volume) : "")

            moveBox(fvg.fvgBoxNegative, mirrorVolumeBars ? startX : curEndXHigh, fvg.info.min, mirrorVolumeBars ? curEndXLow : maxEndX, (fvg.info.min + fvg.info.max) / 2)
            box.set_text(fvg.fvgBoxNegative, showHighLowBoxText ? str.tostring(fvg.info.lowVolume, format.volume) : "")

            moveLine(fvg.fvgSeperator, volumeBarsLeftSide ? startX : maxEndX, (fvg.info.min + fvg.info.max) / 2, volumeBarsLeftSide ? maxEndX : startX)

            line.set_xy1(fvg.fvgTextSeperator, volumeBarsLeftSide ? maxEndX : startX, fvg.info.max)
            line.set_xy2(fvg.fvgTextSeperator, volumeBarsLeftSide ? maxEndX : startX, fvg.info.min)
    
    // IFVG
    if fvg.info.isInverse and ifvgEnabled
        inverseColor = fvg.info.isBull ? bearishInverseColor : bullishInverseColor
        fvg.ifvgBox := createFVGBox(inverseColor)
        startTimeIFVG = ifvgFull ? fvg.info.startTime : fvg.info.endTime
        inverseZoneSize = na(fvg.info.inverseEndTime) ? ((time + 1) - startTimeIFVG) : ((fvg.info.inverseEndTime - curTFMMS * 1000) - startTimeIFVG)
        moveBox(fvg.ifvgBox, startTimeIFVG, fvg.info.max, startTimeIFVG + inverseZoneSize, fvg.info.min)
        IFVGText = (na(fvg.info.combinedTimeframesStr) ? formatTimeframeString(fvg.info.timeframeStr) : fvg.info.combinedTimeframesStr) + " IFVG"
        box.set_text(fvg.ifvgBox, (ifvgVolumetricInfo ? str.tostring(fvg.info.inverseVolume, format.volume) + "\n" : "") + (combinedText and fvg.info.combined ? "[Combined]\n" : "") + IFVGText)

isFVGValid (FVGInfo FVGInfoF) =>
    valid = true
    if (not showInvalidated) and (not na(FVGInfoF.endTime))
        valid := false
    else if FVGInfoF.disabled
        valid := false
    valid

isIFVGValid (FVGInfo FVGInfoF) =>
    valid = true
    if not ifvgEnabled
        valid := false
    else if (not showInvalidated) and (not na(FVGInfoF.inverseEndTime))
        valid := false
    else if FVGInfoF.disabled
        valid := false
    valid

isFVGValidInTimeframe (FVGInfo FVGInfoF) =>
    valid = true
    if (not showInvalidated) and (not na(FVGInfoF.endTime))
        valid := false
    else if FVGInfoF.disabled
        valid := false
    else if not na(FVGInfoF.endBarIndex) and (FVGInfoF.endBarIndex - FVGInfoF.startBarIndex) < minimumFVGSize
        valid := false
    else if na(FVGInfoF.endBarIndex) and deleteUntouched and (bar_index - FVGInfoF.lastTouched) > deleteUntouchedAfterXBars
        valid := false
    valid

isIFVGValidInTimeframe (FVGInfo FVGInfoF) =>
    valid = true
    if (not ifvgEnabled) and (not showInvalidated)
        valid := false
    else if (not showInvalidated) and (not na(FVGInfoF.inverseEndIndex))
        valid := false
    else if not na(FVGInfoF.inverseEndIndex) and (FVGInfoF.inverseEndIndex - FVGInfoF.endBarIndex) < minimumIFVGSize
        valid := false
    else if na(FVGInfoF.inverseEndIndex) and deleteUntouched and (bar_index - FVGInfoF.lastTouchedIFVG) > deleteUntouchedAfterXBars
        valid := false
    valid

arrHasFVG (FVG[] arr, FVG fvgF) =>
    hasFVG = false
    if arr.size() > 0
        for i = 0 to arr.size() - 1
            FVG fvg1 = arr.get(i)
            if fvg1.info.startTime == fvgF.info.startTime
                hasFVG := true
                break
    hasFVG

arrHasIFVG (FVG[] arr, FVG fvgF) =>
    hasIFVG = false
    if arr.size() > 0
        for i = 0 to arr.size() - 1
            FVG fvg1 = arr.get(i)
            if fvg1.info.isInverse
                if fvg1.info.startTime == fvgF.info.startTime
                    hasIFVG := true
                    break
    hasIFVG

handleFVGsFinal () => 
    FVG[] newFVGsToAdd = array.new<FVG>(0)

    alertTimeFVG = ""
    newBullishFVGAlert = false
    newBearishFVGAlert = false

    alertTimeIFVG = ""
    newBullishIFVGAlert = false
    newBearishIFVGAlert = false

    // Add Timeframe FVGs
    for i = 0 to timeframeInfos.size() - 1
        curTimeframe = timeframeInfos.get(i)
        if not curTimeframe.isEnabled
            continue
        if not na(curTimeframe.FVGInfoList)
            if curTimeframe.FVGInfoList.size() > 0
                log.info("Cur TF FVGs : " + str.tostring(curTimeframe.FVGInfoList.size()))
                for j = 0 to curTimeframe.FVGInfoList.size() - 1
                    newFVG = createFVG(FVGInfo.copy(curTimeframe.FVGInfoList.get(j)))
                    newFVG.info.timeframeStr := curTimeframe.timeframeStr
                    newFVGsToAdd.unshift(newFVG)

    // Check New FVGs
    if newFVGsToAdd.size () > 0
        for i = 0 to newFVGsToAdd.size() - 1
            curFVG = newFVGsToAdd.get(i)
            if not curFVG.info.isInverse
                if not arrHasFVG(allFVGList, curFVG)
                    alertTimeFVG := curFVG.info.timeframeStr
                    if curFVG.info.isBull
                        newBullishFVGAlert := true
                    else
                        newBearishFVGAlert := true
            
            else if curFVG.info.isInverse
                if not arrHasIFVG(allFVGList, curFVG)
                    alertTimeIFVG := curFVG.info.timeframeStr
                    if curFVG.info.isBull
                        newBearishIFVGAlert := true
                    else
                        newBullishIFVGAlert := true
    
    // Delete Old FVGs
    if allFVGList.size () > 0
        for i = 0 to allFVGList.size() - 1
            safeDeleteFVG(allFVGList.get(i))
    allFVGList.clear()

    // Add New FVGs
    if newFVGsToAdd.size () > 0
        for i = 0 to newFVGsToAdd.size() - 1
            allFVGList.unshift(newFVGsToAdd.get(i))

    totalFoundFVGs = allFVGList.size()

    // Render FVGs
    renderedFVGCount = 0
    if allFVGList.size() > 0
        for i = 0 to allFVGList.size() - 1
            curFVG = allFVGList.get(i)
            if not isFVGValid(curFVG.info) and (not isIFVGValid(curFVG.info))
                continue
            if curFVG.isRendered
                safeDeleteFVG(curFVG)
            renderedFVGCount += 1
            if DEBUG
                renderFVG(curFVG)
    
    if DEBUG
        log.info("Rendered " + str.tostring(renderedFVGCount) + " / " + str.tostring(totalFoundFVGs) + " FVGs.")
    
    [alertTimeFVG, newBullishFVGAlert, newBearishFVGAlert, alertTimeIFVG, newBullishIFVGAlert, newBearishIFVGAlert]

reqSeq (timeframeStr) =>
    [FVGInfoListF] = request.security(syminfo.tickerid, timeframeStr, [FVGInfoList], calc_bars_count = 250)
    [FVGInfoListF]

getTFData (timeframeInfo timeframeInfoF, timeframeStr) =>
    if not isTimeframeLower(timeframeInfoF.timeframeStr, timeframe.period) and timeframeInfoF.isEnabled
        [FVGInfoListF] = reqSeq(timeframeStr)
        [FVGInfoListF]
    else
        [na]

handleTimeframeInfo (timeframeInfo timeframeInfoF, FVGInfoList) =>
    if not isTimeframeLower(timeframeInfoF.timeframeStr, timeframe.period) and timeframeInfoF.isEnabled
        timeframeInfoF.FVGInfoList := FVGInfoList

//#region Find FVGs
if insideBounds
    shortVol = ta.sma(volume, 5)
    longVol = ta.sma(volume, 15)

    bearCondition = false
    bullCondition = false

    shortTerm = volCheck ? shortVol : 1
    longTerm = volCheck ? longVol : 0

    firstBarSize = math.max(open, close) - math.min(open, close)
    secondBarSize = math.max(open[1], close[1]) - math.min(open[1], close[1])
    thirdBarSize = math.max(open[2], close[2]) - math.min(open[2], close[2])
    barSizeSum = firstBarSize + secondBarSize + thirdBarSize

    barSizeCheck = true
    //if (secondBarSize < math.max(firstBarSize, thirdBarSize))
        //barSizeCheck := false

    fvgBarsCheck = false
    if fvgBars == "Same Type"
        if (open > close and open[1] > close[1] and open[2] > close[2]) or (open <= close and open[1] <= close[1] and open[2] <= close[2])
            fvgBarsCheck := true
    else
        fvgBarsCheck := true

    if fvgBarsCheck and barSizeCheck
        maxCODiff = math.max(math.abs(close[2] - open[1]), math.abs(close[1] - open))
        if fvgFilterMethod == "Average Range"
            bearCondition := ((not fvgSensEnabled) or (barSizeSum * fvgSensitivity > atr / 1.5)) and (allowGaps or (maxCODiff <= atr))
            bullCondition := ((not fvgSensEnabled) or (barSizeSum * fvgSensitivity > atr / 1.5)) and (allowGaps or (maxCODiff <= atr))
        else if fvgFilterMethod == "Volume Threshold"
            thresholdMultiplier = (volumeThresholdPercent / 100.0)
            bearCondition := shortTerm > longTerm * thresholdMultiplier and (allowGaps or (maxCODiff <= atr))
            bullCondition := shortTerm > longTerm * thresholdMultiplier and (allowGaps or (maxCODiff <= atr))

    bearFVG = high < low[2] and close[1] < low[2] and bearCondition
    bullFVG = low > high[2] and close[1] > high[2] and bullCondition

    volSum3 = math.sum(volume, 3)
    float totalVolume = volCheck ? volSum3 : 0
    FVGInfo newFVGInfo = bearFVG ? createFVGInfo(low[2], high, false, time, totalVolume) : bullFVG ? createFVGInfo(low, high[2], true, time, totalVolume) : na
    FVGSize = bearFVG ? math.abs(low[2] - high) : bullFVG ? math.abs(low - high[2]) : 0

    FVGSizeEnough = (FVGSize * fvgSensitivity > atr)
    if FVGSizeEnough
        if not na(newFVGInfo)
            //newFVGInfo.genesisTime := time[2]
            //newFVGInfo.startTime := time

            newFVGInfo.genesisTime := bullFVG ? findValRtnTime(newFVGInfo.min, 2, "High", "Nearest") : findValRtnTime(newFVGInfo.max, 2, "Low", "Nearest")
            newFVGInfo.startTime := bullFVG ? findValRtnTime(newFVGInfo.max, 0, "Low", "Nearest") : findValRtnTime(newFVGInfo.min, 0, "High", "Nearest")

            newFVGInfo.startBarIndex := bar_index - (startZoneFrom == "First Bar" ? 2 : 0)
            newFVGInfo.lastTouched := bar_index
            if bearFVG
                newFVGInfo.lowVolume := volCheck ? (volume + volume[1]) : 0
                newFVGInfo.highVolume := volCheck ? volume[2] : 0
            else
                newFVGInfo.lowVolume := volCheck ? volume[2] : 0
                newFVGInfo.highVolume := volCheck ? (volume + volume[1]) : 0

        if not na(newFVGInfo)
            FVGInfoList.unshift(newFVGInfo)
            while FVGInfoList.size() > showLastXFVGs
                FVGInfoList.pop()

    // Find Closed FVGs
    if FVGInfoList.size () > 0
        for i = 0 to FVGInfoList.size() - 1

            curFVG = FVGInfoList.get(i)
            if curFVG.isInverse
                curFVG.afterInverseMax := math.max(nz(curFVG.afterInverseMax, high), high)
                curFVG.afterInverseMin := math.min(nz(curFVG.afterInverseMin, low), low)
            
            // Is Touched FVG
            if ((curFVG.isBull) and low <= curFVG.max) or ((not curFVG.isBull) and high >= curFVG.min)
                curFVG.lastTouched := bar_index
            
            if ((not curFVG.isBull) and low <= curFVG.max) or ((curFVG.isBull) and high >= curFVG.min)
                curFVG.lastTouchedIFVG := bar_index

            // IFVG Close
            if curFVG.isInverse and na(curFVG.inverseEndIndex)
                if (not curFVG.isBull) and (ifvgEndMethod == "Wick" ? low < curFVG.min : close < curFVG.min)
                    curFVG.inverseEndIndex := bar_index
                    curFVG.inverseEndTime := time_close
                
                if curFVG.isBull and (ifvgEndMethod == "Wick" ? high > curFVG.max : close > curFVG.max)
                    curFVG.inverseEndIndex := bar_index
                    curFVG.inverseEndTime := time_close
            
            if na(curFVG.endBarIndex)                
                // FVG End
                if curFVG.isBull and (fvgEndMethod == "Wick" ? low < curFVG.min : close < curFVG.min)
                    curFVG.endBarIndex := bar_index
                    [endLow, endHigh, endTime] = findValRtnAll(curFVG.min, 0, "Low", "Lower")
                    curFVG.endTime := endTime
                    curFVG.endLow := endLow
                    curFVG.endHigh := endHigh
                    curFVG.afterInverseMax := math.max(nz(curFVG.afterInverseMax, endHigh), endHigh, curFVG.max)
                    curFVG.afterInverseMin := math.min(nz(curFVG.afterInverseMin, endLow), endLow, curFVG.min)
                    curFVG.isInverse := true
                    curFVG.inverseVolume := nz(volume)
                    curFVG.lastTouchedIFVG := bar_index
                
                if (not curFVG.isBull) and (fvgEndMethod == "Wick" ? high > curFVG.max : close > curFVG.max)
                    curFVG.endBarIndex := bar_index
                    [endLow, endHigh, endTime] = findValRtnAll(curFVG.max, 0, "High", "Higher")
                    curFVG.endTime := endTime
                    curFVG.endLow := endLow
                    curFVG.endHigh := endHigh
                    curFVG.afterInverseMax := math.max(nz(curFVG.afterInverseMax, endHigh), endHigh, curFVG.max)
                    curFVG.afterInverseMin := math.min(nz(curFVG.afterInverseMin, endLow), endLow, curFVG.min)
                    curFVG.isInverse := true
                    curFVG.inverseVolume := nz(volume)
                    curFVG.lastTouchedIFVG := bar_index
            
    // Remove Old FVGs
    FVGInfostoRemove = array.new<int>(0)

    if FVGInfoList.size() > 0
        for i = 0 to FVGInfoList.size() - 1
            curIndex = FVGInfoList.size() - 1 - i
            curFVGInfo = FVGInfoList.get(curIndex)
            if (not curFVGInfo.isInverse) and not isFVGValidInTimeframe(curFVGInfo)
                FVGInfostoRemove.push(curIndex)
            if curFVGInfo.isInverse and not isIFVGValidInTimeframe(curFVGInfo)
                FVGInfostoRemove.push(curIndex)

    if FVGInfostoRemove.size () > 0
        for i = 0 to FVGInfostoRemove.size() - 1
            deleteIndex = FVGInfostoRemove.get(i)
            FVGInfoList.remove(deleteIndex)
//#endregion
var array<FVGInfo> FVGInfoListTimeframe1 = na

if insideBounds
    [_FVGInfoListTimeframe1] = getTFData(timeframeInfos.get(0), timeframe1)
    FVGInfoListTimeframe1 :=_FVGInfoListTimeframe1

if insideBounds
    handleTimeframeInfo(timeframeInfos.get(0), FVGInfoListTimeframe1)

    [alertTimeFVG, newBullishFVGAlert, newBearishFVGAlert, alertTimeIFVG, newBullishIFVGAlert, newBearishIFVGAlert] = handleFVGsFinal()

var lastFVGLineTop = line.new(na, na, na, na, xloc = xloc.bar_time, style = line.style_dashed)
var lastFVGLineMiddle = line.new(na, na, na, na, xloc = xloc.bar_time, style = line.style_dashed)
var lastFVGLineMiddleL = line.new(na, na, na, na, xloc = xloc.bar_time, style = line.style_dashed)
var lastFVGLineBottom = line.new(na, na, na, na, xloc = xloc.bar_time, style = line.style_dashed)

var lastFVGBoxTop = box.new(na, na, na, na, xloc = xloc.bar_time, border_color = color.new(color.black, 100))
var lastFVGBoxBottom = box.new(na, na, na, na, xloc = xloc.bar_time, border_color = color.new(color.black, 100))
var lastFVGBoxLine = line.new(na, na, na, na, xloc = xloc.bar_time, style = line.style_solid)

var lastFVGWickTop = line.new(na, na, na, na, xloc = xloc.bar_time, style = line.style_solid)
var lastFVGWickBottom = line.new(na, na, na, na, xloc = xloc.bar_time, style = line.style_solid)

mitigationStatus = "None"

if barstate.islast
    if allFVGList.size() > 0
        for i = 0 to allFVGList.size() - 1
            lastFVG = allFVGList.get(i)
            if lastFVG.info.isInverse
                // Lines
                lastFVGLineTop.set_color((not lastFVG.info.isBull) ? color.new(fvgBullColor, 0) : color.new(fvgBearColor, 0))
                lastFVGLineTop.set_xy1((not lastFVG.info.isBull) ? lastFVG.info.genesisTime : lastFVG.info.startTime, lastFVG.info.max)
                lastFVGLineTop.set_xy2(time(timeframe.period, -lineOffset), lastFVG.info.max)

                lastFVGLineBottom.set_color((not lastFVG.info.isBull) ? color.new(fvgBullColor, 0) : color.new(fvgBearColor, 0))
                lastFVGLineBottom.set_xy1((not lastFVG.info.isBull) ? lastFVG.info.startTime : lastFVG.info.genesisTime, lastFVG.info.min)
                lastFVGLineBottom.set_xy2(time(timeframe.period, -lineOffset), lastFVG.info.min)

                lastFVGLineMiddle.set_color((not lastFVG.info.isBull) ? fvgBullColor : fvgBearColor)
                lastFVGLineMiddle.set_xy1(lastFVG.info.endTime, (lastFVG.info.min + lastFVG.info.max) / 2.0)
                lastFVGLineMiddle.set_xy2(time(timeframe.period, -lineOffset), (lastFVG.info.min + lastFVG.info.max) / 2.0)

                lastFVGLineMiddleL.set_color((not lastFVG.info.isBull) ? fvgBullColor : fvgBearColor)
                lastFVGLineMiddleL.set_xy1(lastFVG.info.endTime, (not lastFVG.info.isBull) ? lastFVG.info.endHigh : lastFVG.info.endLow)
                lastFVGLineMiddleL.set_xy2(lastFVG.info.endTime, (lastFVG.info.min + lastFVG.info.max) / 2.0)

                // Boxes
                lastFVGBoxTop.set_bgcolor((not lastFVG.info.isBull) ? fvgBullColor : fvgBearColor)
                lastFVGBoxTop.set_lefttop(time(timeframe.period, -lineOffset), lastFVG.info.max)
                lastFVGBoxTop.set_rightbottom(time(timeframe.period, -(lineOffset + fvgWidth)), (lastFVG.info.max + lastFVG.info.min) / 2.0)

                lastFVGBoxBottom.set_bgcolor((not lastFVG.info.isBull) ? fvgBullColor : fvgBearColor)
                lastFVGBoxBottom.set_lefttop(time(timeframe.period, -lineOffset), (lastFVG.info.max + lastFVG.info.min) / 2.0)
                lastFVGBoxBottom.set_rightbottom(time(timeframe.period, -(lineOffset + fvgWidth)), lastFVG.info.min)

                lastFVGWickTop.set_color((not lastFVG.info.isBull) ? fvgBullColor : fvgBearColor)
                lastFVGWickTop.set_xy1(time(timeframe.period, -(lineOffset + fvgWidth / 2)), lastFVG.info.max)
                lastFVGWickTop.set_xy2(time(timeframe.period, -(lineOffset + fvgWidth / 2)), lastFVG.info.afterInverseMax)

                lastFVGWickBottom.set_color((not lastFVG.info.isBull) ? fvgBullColor : fvgBearColor)
                lastFVGWickBottom.set_xy1(time(timeframe.period, -(lineOffset + fvgWidth / 2)), lastFVG.info.min)
                lastFVGWickBottom.set_xy2(time(timeframe.period, -(lineOffset + fvgWidth / 2)), lastFVG.info.afterInverseMin)

                bool bordersEnabled = na(lastFVG.info.inverseEndTime)
                if bordersEnabled
                    lastFVGBoxLine.set_color(color.new(fvgBullColor, 100))
                    lastFVGBoxBottom.set_border_color((not lastFVG.info.isBull) ? color.new(fvgBullColor, 0) : color.new(fvgBearColor, 0))
                    lastFVGBoxTop.set_border_color((not lastFVG.info.isBull) ? color.new(fvgBullColor, 0) : color.new(fvgBearColor, 0))
                else
                    lastFVGBoxBottom.set_border_color(color.new(fvgBullColor, 100))
                    lastFVGBoxTop.set_border_color(color.new(fvgBullColor, 100))
                    lastFVGBoxLine.set_color((not lastFVG.info.isBull) ? color.new(fvgBullColor, 0) : color.new(fvgBearColor, 0))
                    lastFVGBoxLine.set_xy1(time(timeframe.period, -lineOffset), (lastFVG.info.max + lastFVG.info.min) / 2.0)
                    lastFVGBoxLine.set_xy2(time(timeframe.period, -(lineOffset + fvgWidth)), (lastFVG.info.max + lastFVG.info.min) / 2.0)

                mitigationStatus := na(lastFVG.info.inverseEndIndex) ? "Valid" : "Mitigated"
                break

if tableEnabled
    var table realtimeTable = table.new(getPosition(tableLocation), 2, 10, bgcolor = screenerColor, frame_width = 2, frame_color = frameColor, border_width = 1, border_color = borderColor)
    // Header
    table.merge_cells(realtimeTable, 0, 0, 1, 0)
    table.cell(realtimeTable, 0, 0, "Higher TF IFVG", text_color = textColor, bgcolor = screenerColor)

    // FVG
    table.cell(realtimeTable, 0, 1, "Status", text_color = textColor, bgcolor = screenerColor)
    table.cell(realtimeTable, 1, 1, mitigationStatus, text_color = mitigationStatus == "Mitigated" ? color.new(fvgBearColor, 0) : color.new(fvgBullColor, 0), bgcolor = mitigationStatus == "Mitigated" ? fvgBearColor : fvgBullColor)

if barstate.isconfirmed
    initRun := false